#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <pthread.h>


#define MAX_NUMBER_OF_CLIENTS 5
#define MAX_IP_LENGTH 16
#define CONFIG_FILE_PATH "./configS"
#define LISTENING_PORT "Escuchando por puerto: %s\n"
#define STRING_LENGTH 255
#define PORT "6000"
#define IP "192.168.10.1"

/** Error messages*/
#define ERROR_AT_OPENING_FILE "Error al intentar abrir el archivo configuracion\n"
#define ERROR_AT_ASSIGNING_SOCKET "error al asignar el socket\n"
#define ERROR_AT_CREATING_SOCKET "error al crear el socket\n"
#define ERROR_AT_TRYING_TO_ACCEPT_SOCKET "error al intentar de acceptar un socket\n"
#define ERROR_CREATING_THREAD "Error al intentar crear el thread"

typedef struct {
	char blackList[MAX_NUMBER_OF_CLIENTS][MAX_IP_LENGTH];
	int maxAllowedClients;
	int totalBlackListed;
} fileConfig;

typedef struct {
	int socketId;
	int threadId;
	int clientId;
} clientData;

typedef struct {
	int quantityOfClients;
	clientData *poolClients;
} poolOfClients;

poolOfClients clients;

/** Internal logging */
void internalLogging(fileConfig configuration) {
	printf("Max allowed %d\n", configuration.maxAllowedClients);
	int p = 0;
	for (; p < configuration.totalBlackListed; p++) {
		printf("%s\n", configuration.blackList[p]);
	}
}


/** check for configuration end of file */
int endOfFile(int fd, char buffer[MAX_IP_LENGTH]) {
	return read(fd, buffer, 1) > 0;
}

/** reads configuration file */
fileConfig readConfiguration() {
	int fd, p = 0, maxAllow = 0;
	char aux[255] ="";
	char *buffer = (char *)malloc(sizeof(255));
	fileConfig configFile;
	fd = open(CONFIG_FILE_PATH, O_RDONLY, 0777);
	if (fd == 0) {
		perror(ERROR_AT_OPENING_FILE);
		exit(-1);
	}
	while (endOfFile(fd, buffer)) {
			if (strcmp(buffer,"\n") != 0){
				strcat(aux,buffer);
			}
			else
			{
				strcat(aux,"\0");
				if (maxAllow == 0)
				{
					configFile.maxAllowedClients = atoi(aux);
					maxAllow = 1;
				}
				else{
					strcpy(configFile.blackList[p], aux);
					p++;
				}
				strcpy(aux,"");
			}
	}
	configFile.totalBlackListed = p;

	return configFile;
}


/** Initialize socket */
int initializeSocket() {
	int sock;
	struct sockaddr_in socketStruct;
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock != -1) {
		socketStruct.sin_family = AF_INET;
		socketStruct.sin_port = htons(atoi(PORT));
		socketStruct.sin_addr.s_addr = htonl(INADDR_ANY);

		if (bind(sock, (struct sockaddr *) &socketStruct, sizeof(socketStruct))
				!= -1)
			printf(LISTENING_PORT, PORT);
		else {
			perror(ERROR_AT_ASSIGNING_SOCKET);
			exit(-1);
		}
	} else {
		perror(ERROR_AT_CREATING_SOCKET);
		exit(-1);
	}
	return sock;
}

/** Client Execution */
void *clientExecution(void *socketStruct);

/** main application */
int main() {
	struct sockaddr_in socketStructClient;
	fileConfig configuration = readConfiguration();
	clientData configClient[configuration.maxAllowedClients];
	internalLogging(configuration);
	
	int sock = initializeSocket(),sockClient;
	listen(sock, configuration.maxAllowedClients);
	pthread_t thread[configuration.maxAllowedClients];
	int clientId = 0;//threadCounter up to maxAllowedClients
	clients.poolClients = configClient;	
	while (1)
	{
		socklen_t lenSock = sizeof(struct sockaddr_in);
		sockClient = accept(sock, (struct sockaddr*) &socketStructClient,&lenSock);
		if (sockClient < 0)
		{
			perror(ERROR_AT_TRYING_TO_ACCEPT_SOCKET);
			exit(-1);
		}
		else
		{
			configClient[clientId].clientId = clientId;
			configClient[clientId].socketId = sockClient;
			if((configClient[clientId].threadId = (pthread_create(&thread[clientId],NULL, clientExecution, &configClient[clientId]))))
			{
						perror(ERROR_CREATING_THREAD);
			}
			
			clientId++;
			clients.quantityOfClients = clientId;
		}
	}
	close(sock);

	return 0;
}

void *clientExecution(void *socketStruct)
{
	int i,daemon = 1;
	clientData * configClient= (clientData *) socketStruct;
	char clientCommand[STRING_LENGTH], servCommand[STRING_LENGTH];
	while(daemon == 1)
	{
			read(configClient->socketId, clientCommand, STRING_LENGTH);
			if (strcmp(clientCommand,"list")==0)
			{
				for(i = 0; i < clients.quantityOfClients;i++)
					sprintf(servCommand,"%s[%d] socketId: %d, threadId: %d\n",servCommand,i,clients.poolClients[i].socketId,clients.poolClients[i].threadId);
			}
			else if(strcmp(clientCommand,"e")==0)
				daemon = 0;
			write(configClient->socketId, servCommand, sizeof(servCommand));
			servCommand[0] = 0;
	}
	close(configClient->socketId);
	pthread_exit(NULL);
}
